#include <Windows.h>
#include "world.h"
#include "physics.h"
#include "D3DGraphics.h"
#include "keyboard.h"
#include <cmath>
#include <ctime>

world_t::world_t()
{
	/* Seed the random number generator */
	srand( (unsigned int)time( NULL ) );

	/* Create World, view, and movable area */
	world_rect = rectangle_screen_t( coordinate_t(0,0), 2000, 1000 );
	view_rect = rectangle_screen_t( coordinate_t(0,0), SCREENWIDTH, SCREENHEIGHT );
	main_unit_box = rectangle_screen_t( coordinate_t(0,0), SCREENWIDTH/6, SCREENHEIGHT/6 );

	/* Create main unit */
	float radius = 10.0;
	/* Put the unit in a random position inside the world */
	float pos_x = (float)( radius + rand() % (int)( world_rect.width - radius*2 ) );
	float pos_y = (float)( radius + rand() % (int)( world_rect.height - radius*2 ) );
	color_t color( 100, 200, 100 );
	
	main_unit = main_unit_t(state_t( vector_2D_t(pos_x, pos_y), vector_2D_t(0.0, 0.0)), radius, color);
	
	/* Create 100 AI units */
	for( int i = 0; i < 100; i++ )
	{
		float radius = 10.0+rand()%100;
		float pos_x = (float)( radius + rand() % (int)( world_rect.width - radius*2 ) );
		float pos_y = (float)( radius + rand() % (int)( world_rect.height - radius*2 ) );
		ai_unit_t ai_unit( state_t(vector_2D_t(pos_x, pos_y), vector_2D_t(0.0, 0.0)), radius, color_t() );
		ai_units.push_back( ai_unit );
	}

	/* Initialize time */
	delta_time = 0.01; // always move at max 10ms at a time (it could happen that we move less than this though)
	accumulator = 0.0; // How much more time do we have to elapse?
	previous_time = clock();
}

void world_t::process_frame( D3DGraphics &gfx, KeyboardClient &kbd )
{
	/* Get keyboard input and process it */
	process_keyboard_input( kbd );

	/* Elapse time */
	/* The goal is to move in fixed delta_time intervals. This is to avoid too long delta_times, such that collisions are skipped, and other unexpected behaviour.
		However, this is only possible if we interpolate between states. Instead, we say that the max time step is delta_time.
		Avoid spiral of death by setting a max time. The game will then slow down if the calculations are too slow.
	*/

	time_t current_time = clock();
	time_t frame_time = current_time - previous_time;
	double frame_time_secs = frame_time/double(CLOCKS_PER_SEC);
	if( frame_time_secs > 0.25 ) frame_time_secs = 0.25;
	previous_time = current_time;

	accumulator += frame_time_secs;

	while( accumulator >= delta_time )
	{
		elapse_time(delta_time);
		resolve_collisions();
		accumulator -= delta_time;
	}
	elapse_time(accumulator); // Eat the rest of the remaining time, even though it should now be less than delta_time
	resolve_collisions();
	
	/* Write graphics to screen */
	construct_screen_graphics( gfx );
}

void world_t::process_keyboard_input( KeyboardClient &kbd )
{
	/* Set the velocity of our main unit according to the keyboard input */
	/* This function should be divided in other functions */
	/* I guess this function is only ever used by the main_unit */

	main_unit.state.v.y = 0;
	main_unit.state.v.x = 0;

	if( kbd.KeyIsPressed(VK_DOWN) )
	{
		main_unit.state.v.y = 200; // 200 pixels / second - later this has to be based on physical properties
	}
	else if( kbd.KeyIsPressed(VK_UP) )
	{
		main_unit.state.v.y = -200;
	}

	if( kbd.KeyIsPressed(VK_LEFT) )
	{
		main_unit.state.v.x = -200;
	}
	else if( kbd.KeyIsPressed(VK_RIGHT) )
	{
		main_unit.state.v.x = 200;
	}
}

void world_t::elapse_time(double delta_time)
{
	/* Move the main unit */
	/* Here we use eulers method, which works well when there is no acceleration. If we have acceleration, we should use runge-kutta as integrator */
	main_unit.state.s.x += (float)( main_unit.state.v.x*delta_time );
	main_unit.state.s.y += (float)( main_unit.state.v.y*delta_time );
}

void world_t::resolve_collisions()
{
	/* Check for collision between unit and world boundary */
	if( main_unit.state.s.x + main_unit.body.radius >= world_rect.width ) main_unit.state.s.x = world_rect.width - main_unit.body.radius;
	if( main_unit.state.s.x - main_unit.body.radius < 0 ) main_unit.state.s.x = main_unit.body.radius;
	if( main_unit.state.s.y + main_unit.body.radius >= world_rect.height ) main_unit.state.s.y = world_rect.height - main_unit.body.radius;
	if( main_unit.state.s.y - main_unit.body.radius < 0 ) main_unit.state.s.y = main_unit.body.radius;

	/* Loop through AI units, check for collision, and resolve the collisions */
	/* Here we have to eventually check for collisions between the AI units, and other obstacles as well. */
	for( auto it = ai_units.begin(); it != ai_units.end(); it++ )
	{
		if( main_unit.in_collision( *it ) )
		{
			it->color = color_t( rand()%256, rand()%256, rand()%256 );
		
			/* How to resolve collisions? */
			// - Revert back time, and move a smaller amount
			// - What about things that move? They should be pushed away, but also depending on how much they weigh etc
			// - Things that don't move like walls should have "infinite" mass
			// - Take into account rotation as well?
		}
	}
}


void world_t::construct_screen_graphics( D3DGraphics &gfx )
{
	// Set what position in the world we want to show to the screen
	set_view();

	// Draw the background
	draw_background( gfx );

	// Draw the main unit to screen
	main_unit.draw( gfx, view_rect );
	
	// Draw the ai units
	for( auto it = ai_units.begin(); it != ai_units.end(); it++ )
	{
		it->draw( gfx, view_rect );
	}
}

void world_t::set_view()
{
	// Unit may move freely inside the main unit box
	// The view will always keep the unit box in the center
	// If the unit moves out of the unit box, the box will move, followed by the view
	// VIEW
	// ------------------------------------------
	// |            Main unit box               |
	// |            ------------                |
	// |            |  ------  |                |
	// |            |  |unit|  |                |
	// |            |  ------  |                |
	// |            ------------                |
	// |                                        |
	// ------------------------------------------

	/* First check if the unit has moved outside the box. If so move the box */
	/* Remember that the rectangle coordinates is always specified in integer values.
		The rectangle will therefore be clamped up and left.
		At the same time, the drawing of the circle is a bit shifted (i think to the right?), because it must contain the
		center of the circle, and the diameter is always an even number.
		The drawings and calculations may therefore be off by a pixel in certain places.
	*/

	if( main_unit.state.s.x + main_unit.body.radius > main_unit_box.top_left.x + main_unit_box.width )
	{
		main_unit_box.top_left.x = main_unit.state.s.x + main_unit.body.radius - (main_unit_box.width - 1);
	}
	else if( main_unit.state.s.x - main_unit.body.radius < main_unit_box.top_left.x )
	{
		main_unit_box.top_left.x = main_unit.state.s.x - main_unit.body.radius;
	}

	if( main_unit.state.s.y + main_unit.body.radius > main_unit_box.top_left.y + main_unit_box.height )
	{
		main_unit_box.top_left.y = main_unit.state.s.y + main_unit.body.radius - (main_unit_box.height - 1);
	}
	else if( main_unit.state.s.y - main_unit.body.radius < main_unit_box.top_left.y )
	{
		main_unit_box.top_left.y = main_unit.state.s.y - main_unit.body.radius;
	}

	/* Make sure the box is inside the world */
	clamp_rectangle_to_world( main_unit_box );

	/* Adjust the view so that the box is in the center */

	float center_x = main_unit_box.top_left.x + (main_unit_box.width - 1)/2.0; // X coord in center of box
	float center_y = main_unit_box.top_left.y + (main_unit_box.height - 1)/2.0; // Y coord in center of box

	view_rect.top_left.x = center_x - (SCREENWIDTH-1)/2.0;
	view_rect.top_left.y = center_y - (SCREENHEIGHT-1)/2.0;

	/* Make sure the view is inside the world */
	clamp_rectangle_to_world( view_rect );

}

void world_t::clamp_rectangle_to_world( rectangle_screen_t &rect )
{
	// Here we assume that the top_left coordinates of the world are <0,0>

	if( rect.top_left.x < 0 )
	{
		rect.top_left.x = 0;
	}
	else if( rect.top_left.x + rect.width > world_rect.width )
	{
		rect.top_left.x = world_rect.width - rect.width;
	}

	if( rect.top_left.y < 0 )
	{
		rect.top_left.y = 0;
	}
	else if( rect.top_left.y + rect.height > world_rect.height )
	{
		rect.top_left.y = world_rect.height - rect.height;
	}
}

void world_t::draw_background( D3DGraphics &gfx )
{
	/* Draw a background grid, just to notice that we are actually moving relative to something */
	for( int i = 0; i < world_rect.width; i += 60 )
	{
		if( i >= view_rect.top_left.x && i < (view_rect.top_left.x + view_rect.width) )
		{
			int x = i - view_rect.top_left.x;
			gfx.DrawLine( x, 0, x, SCREENHEIGHT - 1, 200, 200, 200 );
		}
	}

	for( int i = 0; i < world_rect.height; i += 60 )
	{
		if( i >= view_rect.top_left.y && i < (view_rect.top_left.y + view_rect.height) )
		{
			int y = i - view_rect.top_left.y;
			gfx.DrawLine( 0, y, SCREENWIDTH - 1, y, 200, 200, 200 );
		}
	}


	/* Draw the box surrounding the unit, just to see what it looks like */
	// Top horizontal
	gfx.DrawLine( main_unit_box.top_left.x - view_rect.top_left.x, 
					main_unit_box.top_left.y - view_rect.top_left.y, 
					main_unit_box.top_left.x - view_rect.top_left.x + main_unit_box.width - 1, 
					main_unit_box.top_left.y - view_rect.top_left.y,
					200,200, 0);

	// Bottom horizonal
	gfx.DrawLine( main_unit_box.top_left.x - view_rect.top_left.x, 
					main_unit_box.top_left.y - view_rect.top_left.y + main_unit_box.height - 1, 
					main_unit_box.top_left.x - view_rect.top_left.x + main_unit_box.width - 1, 
					main_unit_box.top_left.y - view_rect.top_left.y + main_unit_box.height - 1,
					200,200, 0);
	
	// Left vertical
	gfx.DrawLine( main_unit_box.top_left.x - view_rect.top_left.x, 
					main_unit_box.top_left.y - view_rect.top_left.y, 
					main_unit_box.top_left.x - view_rect.top_left.x, 
					main_unit_box.top_left.y - view_rect.top_left.y + main_unit_box.height - 1,
					200,200, 0);

	// Right vertical
	gfx.DrawLine( main_unit_box.top_left.x - view_rect.top_left.x + main_unit_box.width - 1, 
					main_unit_box.top_left.y - view_rect.top_left.y, 
					main_unit_box.top_left.x - view_rect.top_left.x + main_unit_box.width - 1, 
					main_unit_box.top_left.y - view_rect.top_left.y + main_unit_box.height - 1,
					200,200, 0);	
}